﻿using Microsoft.Azure.KeyVault;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CloudStorageLight.Core
{
    public class AzureBlobAdapter : BlobAdapterBase
    {

        const string keyLastWriteTime = "LastWriteTime";
        const string keyCreationTime = "CreationTime";


        private CloudStorageAccount storageAccount;
        private CloudBlobClient blobClient;
        private CloudBlobContainer fileContainer;
        internal string containerName;

        private BlobEncryptionPolicy encryptionPolicy;

        internal bool IsEncrypted { get; private set; }

        public AzureBlobAdapter(string rootPath, string containerName, string connectionString, string encryptKey = null, string timezone = null)
        {
            this.RootPath = rootPath;
            this.containerName = containerName;
            if (connectionString != null)
            {
                storageAccount = CloudStorageAccount.Parse(connectionString); //CloudConfigurationManager.GetSetting("blobStorage"));
                blobClient = storageAccount.CreateCloudBlobClient();
                fileContainer = blobClient.GetContainerReference(containerName);
                fileContainer.CreateIfNotExists();
            }
            this.TimeZone = string.IsNullOrEmpty(timezone) ? "UTC" : timezone;
            this.IsEncrypted = !string.IsNullOrEmpty(encryptKey);
            if (IsEncrypted)
            {
                encryptKey += "1234567890123456789012345678901234567890";
                var keyBytes = Encoding.UTF8.GetBytes(encryptKey); //256 bits
                if (keyBytes.Length > 32)
                {
                    keyBytes = keyBytes.Take(32).ToArray();
                }
                var symmetricKey = new SymmetricKey("BlobEncryptKey", keyBytes);

                // upload test file.
                encryptionPolicy = new BlobEncryptionPolicy(symmetricKey, null);
            }
        }


        /// <summary>
        /// 指定したファイルが存在するか
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        protected override async Task<bool> ExistsAsyncInternal(string path)
        {
            if (path == "/") return true;
            var item = new BlobItem(Parent, path);
            var blobPath = GetAzureBlobRelativePath(item.Path);
            if (blobPath == "/") return true;

            if (blobPath.EndsWith("/"))
            {
                var list = await blobClient.ListBlobsSegmentedAsync(this.containerName + "/" + blobPath.TrimStart('/'), true, BlobListingDetails.None, 1, null, null, null);
                return list != null && list.Results.Count() > 0;
            }
            else
            {
                var blob = fileContainer.GetBlockBlobReference(blobPath);
                return await blob.ExistsAsync();
            }
        }

        /// <summary>
        /// ディレクトリののファイル一覧を取得する
        /// </summary>
        /// <param name="id"></param>
        /// <param name="maxSize"></param>
        /// <returns></returns>
        public override async Task<List<BlobItem>> GetFilesAsync(string id, int maxSize, bool flat = false)
        {
            var children = new List<BlobItem>();
            BlobContinuationToken token = null;
            var paths = new List<string>();
            while (true)
            {
                var list = await blobClient.ListBlobsSegmentedAsync(this.containerName + "/" + GetAzureBlobRelativePath(id).TrimStart('/'), flat, BlobListingDetails.Metadata, maxSize > 0 ? (int?)(maxSize + 1) : null, token, null, null);
                if (list != null)
                {
                    token = list.ContinuationToken;
                    foreach (var item in list.Results)
                    {
                        var path = BlobUtil.UnescapeDataString(item.Uri.PathAndQuery).Substring(this.fileContainer.Uri.PathAndQuery.Length);
                        if (path == "//") path = "/";
                        paths.Add(path);
                    }
                    foreach (var item in list.Results)
                    {
                        BlobItem newItem = null;
                        var path = BlobUtil.UnescapeDataString(item.Uri.PathAndQuery).Substring(this.fileContainer.Uri.PathAndQuery.Length);
                        if (path == "//") path = "/";
                        var directory = item as CloudBlobDirectory;
                        if (directory != null)
                        {
                            if (path.EndsWith("/") && paths.Contains(path.Substring(0, path.Length - 1))) continue;
                            newItem = new BlobDirectory(Parent, RootPath.TrimEnd('/') + path);
                        }
                        else
                        {
                            var blockBlob = (CloudBlockBlob)item;
                            if (blockBlob.Metadata.ContainsKey(BlobItem.keyIsDeleted)) continue;
                            if (path.EndsWith("/"))
                            {
                                newItem = new BlobDirectory(Parent, RootPath.TrimEnd('/') + path);
                            }
                            else
                            {
                                newItem = new BlobFile(Parent, RootPath.TrimEnd('/') + path);
                            }
                            newItem.Length = blockBlob.Properties.Length;
                            newItem.LastModified = ConvertTimezone(blockBlob.Properties.LastModified);

                            if (blockBlob.Metadata.ContainsKey(keyLastWriteTime)) newItem.LastWriteTime = ConvertTimezone(DateTimeOffset.ParseExact(blockBlob.Metadata[keyLastWriteTime], "O", null));
                            if (blockBlob.Metadata.ContainsKey(keyCreationTime)) newItem.CreationTime = ConvertTimezone(DateTimeOffset.ParseExact(blockBlob.Metadata[keyCreationTime], "O", null));
                        }
                        children.Add(newItem);
                        if (maxSize > 0 && children.Count >= maxSize)
                        {
                            break;
                        }
                    }
                    if (token == null) break;
                }
                else
                {
                    break;
                }
            }
            return children;
        }

        /// <summary>
        /// Blobのコピー
        /// </summary>
        /// <param name="path"></param>
        /// <param name="fromUri"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task CopyAsync(string path, BlobFile file)
        {
            var item = new BlobFile(Parent, path);

            var blobPath = GetAzureBlobRelativePath(item.Path);
            var blob = fileContainer.GetBlockBlobReference(blobPath);
            var filePath = GetAzureBlobRelativePath(file.Path);

            await blob.StartCopyAsync(fileContainer.GetBlockBlobReference(filePath));

        }
        #pragma warning restore 1998

        /// <summary>
        /// 削除
        /// </summary>
        /// <returns></returns>
        public override async Task<bool> DeleteAsync(BlobItem item)
        {
            var blobPath = GetAzureBlobRelativePath(item.Path);

            var blob = fileContainer.GetBlockBlobReference(blobPath);
            if (!await blob.ExistsAsync()) return false;
            await blob.DeleteAsync();
            ClearExistsCahce();
            return true;
        }

        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task UploadAsync(BlobItem item, Stream stream)
        {
            var blobPath = GetAzureBlobRelativePath(item.Path);

            var blob = fileContainer.GetBlockBlobReference(blobPath);
            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.UploadFromStreamAsync(stream, null, blobRequestOptions, null);
            }
            else
            {
                await blob.UploadFromStreamAsync(stream);
            }
            if (item.LastWriteTime.HasValue) blob.Metadata[keyLastWriteTime] = item.LastWriteTime.Value.ToString("O");
            if (item.CreationTime.HasValue) blob.Metadata[keyCreationTime] = item.CreationTime.Value.ToString("O");
            if (item.LastWriteTime.HasValue || item.CreationTime.HasValue) await blob.SetMetadataAsync();
            ClearExistsCahce();
        }

        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        public override async Task UploadAsync(BlobItem item, string localpath)
        {
            var blobPath = GetAzureBlobRelativePath(item.Path);

            var blob = fileContainer.GetBlockBlobReference(blobPath);

            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.UploadFromFileAsync(localpath, null, blobRequestOptions, null);
            }
            else
            {
                await blob.UploadFromFileAsync(localpath);
            }
            if (item.LastWriteTime.HasValue) blob.Metadata[keyLastWriteTime] = item.LastWriteTime.Value.ToString("O");
            if (item.CreationTime.HasValue) blob.Metadata[keyCreationTime] = item.CreationTime.Value.ToString("O");
            if (item.LastWriteTime.HasValue || item.CreationTime.HasValue) await blob.SetMetadataAsync();
            ClearExistsCahce();
           
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, Stream stream)
        {
            var blobPath = GetAzureBlobRelativePath(item.Path);

            var blob = fileContainer.GetBlockBlobReference(blobPath);

            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.DownloadToStreamAsync(stream, null, blobRequestOptions, null);
            }
            else
            {
                await blob.DownloadToStreamAsync(stream);
            }
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, string localpath)
        {
            var path = GetAzureBlobRelativePath(item.Path);
            var blob = fileContainer.GetBlockBlobReference(path);


            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.DownloadToFileAsync(localpath, FileMode.Create, null, blobRequestOptions, null);
            }
            else
            {
                await blob.DownloadToFileAsync(localpath, FileMode.Create);
            }
        }

        private string GetAzureBlobRelativePath(string path)
        {
            if (path == RootPath) return "/";
            return path.Substring(RootPath.Length);
        }

        /// <summary>
        /// メタ情報の設定
        /// </summary>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task SetMetadataAsync(BlobItem item)
        {
            if (RootPath != "/")
            {
                item.Link = GetAzureBlobRelativePath(item.Path);

            }
        }
        #pragma warning restore 1998

        /// <summary>
        /// メタ情報の取得
        /// </summary>
        /// <param name="fetch"></param>
        /// <returns></returns>
        public override async Task GetMetadataAsync(BlobItem item)
        {
            var blobPath = GetAzureBlobRelativePath(item.Path);

            var cloudBlob = this.fileContainer.GetBlockBlobReference(blobPath);
            if (cloudBlob == null) return;
            var exists = await cloudBlob.ExistsAsync();
            if (exists) await cloudBlob.FetchAttributesAsync();
            item.LastModified = ConvertTimezone(cloudBlob.Properties.LastModified);
            item.Length = cloudBlob.Properties.Length;

            if (cloudBlob.Metadata.ContainsKey(keyLastWriteTime)) item.LastWriteTime = ConvertTimezone(DateTimeOffset.ParseExact(cloudBlob.Metadata[keyLastWriteTime], "O", null));
            if (cloudBlob.Metadata.ContainsKey(keyCreationTime)) item.CreationTime = ConvertTimezone(DateTimeOffset.ParseExact(cloudBlob.Metadata[keyCreationTime], "O", null));
        }

        /// <summary>
        /// フォルダ作成
        /// </summary>
        /// <param name="dire"></param>
        /// <returns></returns>
        public override async Task CreateDirectoryAsync(BlobDirectory dire)
        {
            using (var st = new MemoryStream(new byte[] { }))
            {
                await UploadAsync(dire, st);
            }
            ClearExistsCahce();
        }

        /// <summary>
        /// 後処理
        /// </summary>
        public override void Dispose()
        {

        }
    }

}
